package pathfinding;

import java.io.Serializable;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;

import ownserver.ServerShip;
import ownserver.World;


import base.Ship;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.util.ScalableHashMap;
import com.sun.sgs.app.util.ScalableList;

public class SpaceTimeReservationTable implements ManagedObject, Serializable {
	private static final long serialVersionUID = 1L;

	/** The {@link Logger} for this class. */
    private static final Logger logger =
        Logger.getLogger(SpaceTimeReservationTable.class.getName());
    
	// TODO: add priorities
	
	public SpaceTimeReservationTable()
	{
		ScalableHashMap<GridCell, ScalableList<Reservation>> reservations = 
				new ScalableHashMap<GridCell, ScalableList<Reservation>>();
		ScalableHashMap <ServerShip, ScalableList<GridCell>> lists =
				new ScalableHashMap<ServerShip, ScalableList<GridCell>>();
//		ScalableList<ManagedReference<DStarGridDiagSimpleTabled>> watchers = 
//				new ScalableList<ManagedReference<DStarGridDiagSimpleTabled>> ();
		
		DataManager dataManager = AppContext.getDataManager();
        dataManager.markForUpdate(this);
        
        reservationsRef = dataManager.createReference(reservations);
        listsRef = dataManager.createReference(lists);
        //watchersRef = dataManager.createReference(watchers);
	}
	
	/**
	 * Reserve the given grid from startTime to endTime.
	 * 
	 * Multiple same time reservations on a grid are possible.
	 */
	public void reserve(GridCell c, long startTime, long endTime, ManagedReference <ServerShip> reserver)
	{
		ScalableList<Reservation> reslist = reservationsRef.get().get(c);
		boolean newGridReserved = false;
		if (reslist == null)
		{
			// list didn't exist. create it
			reslist = new ScalableList<Reservation> (); 
			reservationsRef.getForUpdate().put(c, reslist);
			newGridReserved = true;
		//	AppContext.getDataManager().markForUpdate(reservationsRef.get());
		}
		//AppContext.getDataManager().markForUpdate(reslist);
		reslist.add(new Reservation(startTime, endTime, reserver));
		
		// add knowledge of reservation 
		addToShipReservationInfo(reserver.get(), c);
		
		if (newGridReserved) {
			reservationCreated(c); // TODO: fix this once our finders start to look correctly into timespace
		}
	}
	
	/**
	 * Remove all reservations for the given grid for the given reserving ship.
	 * Removes empty reservation lists.
	 * 
	 * @param x
	 * @param y
	 * @param reserver
	 */
	public void removeReservations(GridCell c, ManagedReference <ServerShip> reserver) {
		ScalableList<Reservation> reslist = reservationsRef.get().get(c);
		if (reslist == null)
		{
			return;
		}
		
		Reservation r;
		int i=0;
		while (i < reslist.size())
		{
			r = reslist.get(i);
			if (r.reserver.equals(reserver)) {
				reslist.remove(i);
			}
			else
			{
				i++;
			}
		}
		
		if (reslist.isEmpty()) {
			DataManager dataManager = AppContext.getDataManager();
			reservationsRef.getForUpdate().remove(c);
			dataManager.removeObject(reslist);
			// TODO: fix this once our finders start to look correctly into timespace
			reservationRemoved(c);
		}
	}
	
	public void removeReservations(ManagedReference <ServerShip> reserver, boolean removeListToo)
	{
		ScalableList<GridCell> list = listsRef.get().get(reserver.get());
		
		if (list == null)
		{
			return;
		}
		
		while (list.isEmpty() == false)
		{
			removeReservations(list.remove(0), reserver);
		}
		
		if (removeListToo) {
			listsRef.getForUpdate().remove(reserver.get());
			AppContext.getDataManager().removeObject(list);
		}
	}
	
	public void updateReservations(ManagedReference<ServerShip> shipRef) {
		removeReservations(shipRef, false);
		
		addReservations(shipRef);
	}
	
	public void addReservations(ManagedReference<ServerShip> shipRef) {
		ServerShip s = shipRef.get();
		for (GridCell c : GridGeometry.getCells(s)) {
			reserve(c, 0, Long.MAX_VALUE, shipRef);
		}
	}
	
	/**
	 * Is the given grid reserved at any moment from startTime to endTime?
	 */
	public boolean isReserved(int x, int y, long timeStart, long timeEnd) {
		return isReserved(new GridCell(x,y), timeStart, timeEnd);
	}
	
	public boolean isReserved(GridCell c, long timeStart, long timeEnd)
	{
		if (c == null) {
			logger.log(Level.INFO, "received query for a null GridCell");
			return true;
		}
		ScalableList<Reservation> reslist = reservationsRef.get().get(c);
		
		if (reslist == null) {
			// there were no reservations for the grid
			return false;
		}
		
		for (Reservation r: reslist) {
			if (r.start <= timeStart && r.end >= timeStart) {
				// the starting time is between start and end of another reservation
				//logger.log(Level.INFO, c + " was reserved");
				return true;
			}
			if (r.start <= timeEnd && r.end >= timeEnd ) {
				// the ending time is between start and end of another reservation
				//logger.log(Level.INFO, c + " was reserved");
				return true;
			}
		}
		
		return false;
	}
	
	public void addWatcher(ManagedReference<DStarPathfinderSimple> d) {
		watcherRef = d;
		//watchersRef.getForUpdate().add(d);
	}
	
	/*
	public void removeWatcher(ManagedReference<DStarPathfinderSimple> d) {
		//watchersRef.getForUpdate().remove(d);
		
	}
	*/
	
	protected void addToShipReservationInfo(ServerShip s, GridCell c)
	{
		//AppContext.getDataManager().markForUpdate(this);
		ScalableList<GridCell> list = listsRef.get().get(s);
		
		//AppContext.getDataManager().markForUpdate(listsRef.get());
		
		if (list == null)
		{
			list = new ScalableList<GridCell>();
			listsRef.getForUpdate().put(s, list);
		}
		//AppContext.getDataManager().markForUpdate(list);
		list.add(c);
	}

	public String toString()
	{
		String result = new String();
		
		ScalableList<Reservation> res;
		for (GridCell c : reservationsRef.get().keySet()) {
			result += "For " + c + ":";
			for (Reservation r : reservationsRef.get().get(c))
			{
				result += r + ",";
			}
			result += "\n";
		}
		
		return result;
	}
	
	private void reservationCreated(GridCell changedCell)
	{
		if (watcherRef != null)
		{
			watcherRef.get().changedToReserved(changedCell);
		}
		/*
		for (ManagedReference<DStarGridDiagSimpleTabled> d : watchersRef.get()) {
			d.getForUpdate().changedToReserved(changedCell);
			AppContext.getDataManager().markForUpdate(d.get());
		}
		*/
	}
	
	private void reservationRemoved(GridCell changedCell)
	{
		if (watcherRef != null)
		{
			watcherRef.get().changedToFree(changedCell);
		}
		/*
		for (ManagedReference<DStarGridDiagSimpleTabled> d : watchersRef.get()) {
			d.getForUpdate().changedToFree(changedCell);
			AppContext.getDataManager().markForUpdate(d.get()); 
		}
		*/
	}
	
	// TODO: use something else instead of lists. priorityqueues based on the starting time perhaps?
	
	// maps a gridcell to a list of reservations (that have been made for that gridcell)
	ManagedReference<ScalableHashMap <GridCell, ScalableList<Reservation>>> reservationsRef;
	
	// maps a servership to a list of gridcells (from which have been made reservations for the servership)
	ManagedReference<ScalableHashMap <ServerShip, ScalableList<GridCell>>> listsRef;
	
	//ManagedReference<ScalableList<ManagedReference<DStarGridDiagSimpleTabled>>> watchersRef;
	ManagedReference<DStarPathfinderSimple> watcherRef = null;
}
